﻿using System;
using System.Collections.ObjectModel;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.SyntacticAnalysis;
using ParserGenerator.TableCompression;

namespace ParserGenerator.ScannerConstruction
{
    [Serializable]
    public sealed class ScannerAutomatonTable
    {
        public ScannerAutomaton Automaton { get; }
        public ReadOnlyCollection<string> StartNameArray { get; }
        public ReadOnlyCollection<string> TokenNameArray { get; }
        public ReadOnlyCollection<int> StartArray { get; }
        public ReadOnlyCollection<int> FlagArray { get; }
        public CompactTable ActionTable { get; }

        public ScannerAutomatonTable(ScannerAutomaton automaton)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            Automaton = automaton;
            StartNameArray = ComputeStartNameArray();
            TokenNameArray = ComputeTokenNameArray();
            StartArray = ComputeStartArray();
            FlagArray = ComputeFlagArray();
            ActionTable = ComputeActionTable();
        }

        private ReadOnlyCollection<string> ComputeStartNameArray()
        {
            string[] start = new string[Automaton.Starts.Count];
            foreach (var t in Automaton.Starts)
                start[t.Value.Number] = t.Value.Name;
            return start.AsReadOnly();
        }

        private ReadOnlyCollection<string> ComputeTokenNameArray()
        {
            string[] token = new string[Automaton.Tokens.Count];
            foreach (GrammarTerminalSymbol t in Automaton.Tokens)
                token[t.Number] = t.Name;
            return token.AsReadOnly();
        }

        private ReadOnlyCollection<int> ComputeStartArray()
        {
            int[] start = new int[Automaton.Starts.Count * 3];
            foreach (var t in Automaton.Starts)
            {
                start[t.Value.Number * 3] = t.Value.State.Number;
                start[t.Value.Number * 3 + 1] = t.Value.StrictState.Number;
                start[t.Value.Number * 3 + 2] = t.Value.IsExclusive ? 1 : 0;
            }
            return start.AsReadOnly();
        }

        private ReadOnlyCollection<int> ComputeFlagArray()
        {
            int[] flag = new int[Automaton.States.Count * 2];
            foreach (ScannerAutomatonState state in Automaton.States)
            {
                LexicalRuleDeclarator rule = null;
                LexicalRuleDeclarator strictRule = null;
                foreach (LexicalRuleDeclarator item in state.Reductions)
                {
                    if (rule == null)
                        rule = item;
                    if (strictRule == null && !item.AtLineEnd)
                        strictRule = item;
                }
                if (rule != null)
                {
                    GrammarTerminalSymbol sym = Automaton.Tokens.TryGetToken(rule);
                    if (sym != null)
                        flag[state.Number * 2] = sym.Number << 4 | 0x4;
                    else
                        flag[state.Number * 2] = Automaton.States.Reductions[rule] << 4;
                    if (rule.AtLineEnd)
                        flag[state.Number * 2] |= 0x2;
                    flag[state.Number * 2] |= 0x1;
                }
                if (strictRule != null)
                {
                    GrammarTerminalSymbol sym = Automaton.Tokens.TryGetToken(strictRule);
                    if (sym != null)
                        flag[state.Number * 2 + 1] = sym.Number << 4 | 0x4;
                    else
                        flag[state.Number * 2 + 1] = Automaton.States.Reductions[rule] << 4;
                    flag[state.Number * 2 + 1] |= 0x1;
                }
            }
            return flag.AsReadOnly();
        }

        private CompactTable ComputeActionTable()
        {
            CompactTableBuilder table = new CompactTableBuilder(Automaton.CharMap.Classes.Count);
            foreach (ScannerAutomatonState state in Automaton.States)
            {
                CompactTableRowBuilder row = table.DefineRow();
                foreach (var t in state.Transitions)
                    row.Add(t.Key.Number.Value, t.Value.Target.Number);
            }
            return table.Build();
        }
    }
}
